#include "wxstates.h"
#include "wxbase64.h"

const InitialState g_initialState;
const ConnectedState g_connectedState;
const HeloState g_heloState;
const SendMailFromState g_sendMailFromState;
const RcptListState g_rcptListState;
const StartDataState g_startDataState;
const DataState g_dataState;
const QuitState g_quitState;
const ClosedState g_closedState;
const RetryState g_retryState;

const SendUserState  g_userState;
const SendPasswState g_passwState;

void MailState::onTimeout(wxSMTP& context) const
{
    context.OnMessageStatus(wxEmailMessage::dispRetry);
    context.Final();
}

void InitialState::onConnect(wxSMTP& context, wxSocketEvent& event) const
{
    if (event.GetSocketEvent() == wxSOCKET_CONNECTION) {
	context.ChangeState(g_connectedState);
	context.TimerStart(wxSMTP_TIMEOUT_READY);
    } else {
	// error
	context.OnMessageStatus(wxEmailMessage::dispRetry);
	context.Final();
    }
}

void InitialState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
    // Make sure this is same as ConnectedState::onResponse
    g_connectedState.onResponse(context, smtpCode, smtpRsp);
}


void ConnectedState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
    context.TimerStop();

    // Make sure this is same as InitialState::onResponse
    if (smtpCode == 220) {
	    context.ChangeState(g_heloState);
	    // TODO: using some wxWindows function for getting the hostname
	    context.Write(wxT("HELO localhost\x00d\x00a"));
	    context.TimerStart(wxSMTP_TIMEOUT_HELO);
	    return;
    }

	char c=0;
	smtpRsp[0].GetAsChar( &c );

    switch (c) {
	case '4':
	    context.OnMessageStatus(wxEmailMessage::dispRetry);
	    context.ChangeState(g_quitState);
	    context.SendQuit();
	    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
	    break;

	case '5':
	default:
		context.OnSetFailString(smtpRsp);
	    context.OnMessageStatus(wxEmailMessage::dispFail);
	    context.ChangeState(g_quitState);
	    context.SendQuit();
	    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
	    break;
    }
}


void HeloState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
    context.TimerStop();

    if (smtpCode == 250)
	{
		if ( !context.m_user.IsEmpty() )
		{
			context.Write(wxT("AUTH LOGIN ") );

			context.Write( wxBase64::Encode( (const wxUint8*)(const char*)context.m_user.ToAscii(),
				context.m_user.Length()) );

			context.Write( wxT("\x00d\x00a"));
			context.ChangeState(g_userState);
			context.TimerStart(wxSMTP_TIMEOUT_MAIL);
		}
		else
		{
			context.ChangeState(g_sendMailFromState);
			context.SendFrom();
			context.TimerStart(wxSMTP_TIMEOUT_MAIL);
		}
		return;
    }

	char c=0;
	smtpRsp[0].GetAsChar( &c );

    switch (c) {
	case '4':
	    context.OnMessageStatus(wxEmailMessage::dispRetry);
	    context.ChangeState(g_quitState);
	    context.SendQuit();
	    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
	    break;

	case '5':
	default:
		context.OnSetFailString(smtpRsp);
	    context.OnMessageStatus(wxEmailMessage::dispFail);
	    context.ChangeState(g_quitState);
	    context.SendQuit();
	    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
	    break;
    }
}

void SendMailFromState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
    context.TimerStop();

    if (smtpCode == 250)
	{
	    context.ChangeState(g_rcptListState);
	    context.SendNextRecipient();
	    context.TimerStart(wxSMTP_TIMEOUT_RCPT);
	    return;
    }

	char c=0;
	smtpRsp[0].GetAsChar( &c );

    switch (c) {
	case '4':
	    context.OnMessageStatus(wxEmailMessage::dispRetry);
	    context.ChangeState(g_quitState);
	    context.SendQuit();
	    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
	    break;

	case '5':
	default:
		context.OnSetFailString(smtpRsp);
	    context.OnMessageStatus(wxEmailMessage::dispFail);
	    context.ChangeState(g_quitState);
	    context.SendQuit();
	    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
	    break;
    }
}


void RcptListState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
    context.TimerStop();

    if (smtpCode == 421) {
	// 421 has per-message scope, not per-recipient scope
	context.OnMessageStatus(wxEmailMessage::dispRetry);
	context.ChangeState(g_quitState);
	context.TimerStart(wxSMTP_TIMEOUT_QUIT);
	context.SendQuit();
    }

	char c=0;
	smtpRsp[0].GetAsChar( &c );

    switch (c) {
	case '2':
	    context.OnRecipientStatus(wxEmailMessage::dispAccept);
	    break;

	case '4':
	    context.OnRecipientStatus(wxEmailMessage::dispRetry);

	    // trigger early retry
	    context.SetFastRetryMode();

	    //
	    // don't attempt further addrs since we probably
	    // hit limit for this message
	    //
	    goto startdata;

	case '5':
	    if (smtpCode == 552) {

		// see RFC 2821 p.56 - treat as temporary
		context.OnRecipientStatus(wxEmailMessage::dispRetry);

		// trigger early retry
		context.SetFastRetryMode();

		//
		// don't attempt further addrs since we probably
		// hit limit for this message
		//
		goto startdata;
	    }
	    // fall through

	default:
		context.OnSetFailString(smtpRsp);
	    context.OnRecipientStatus(wxEmailMessage::dispFail);
	    break;
    }

    if (context.SendNextRecipient())
	{
		context.TimerStart(wxSMTP_TIMEOUT_RCPT);
		return;
    }

startdata:

    context.Write(wxT("DATA\x00d\x00a"));
    context.ChangeState(g_startDataState);
    context.TimerStart(wxSMTP_TIMEOUT_DATA);
}


void StartDataState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
    context.TimerStop();

    if (smtpCode == 354)
	{
		context.ChangeState(g_dataState);
		context.SendData();
		context.TimerStart(wxSMTP_TIMEOUT_DATAEND);
		return;
    }

    if (smtpRsp[0] == '4')
	{
		context.OnMessageStatus(wxEmailMessage::dispRetry);
    } else
	{
		context.OnSetFailString(smtpRsp);
		context.OnMessageStatus(wxEmailMessage::dispFail);
    }

    context.ChangeState(g_quitState);
    context.SendQuit();
    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
}


void DataState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
    context.TimerStop();

    if (smtpCode == 250) {
	context.OnMessageStatus(wxEmailMessage::dispSucceed);
    } else if (smtpRsp[0] == '4') {
	context.OnMessageStatus(wxEmailMessage::dispRetry);
    } else {
		context.OnSetFailString(smtpRsp);
		context.OnMessageStatus(wxEmailMessage::dispFail);
    }

    context.ChangeState(g_quitState);
    context.SendQuit();
    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
}

void QuitState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
    context.TimerStop();

    context.Final();
}

void QuitState::onTimeout(wxSMTP& context) const
{
    // message has already been accepted for delivery, so no error here
    context.Final();
}

void RetryState::onTimeout(wxSMTP& context) const
{
    context.UpdateElapsed();
    context.SendInner();
}

//////////////////////////////////////////////////////////////////////////////////////////////
void SendUserState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
	context.TimerStop();

	//503 AUTH command used when not advertised
	if ( 503==smtpCode )
	{
		context.TimerStart(wxSMTP_TIMEOUT_HELO);
		context.ChangeState(g_heloState);
		// TODO: using some wxWindows function for getting the hostname
		context.Write(wxT("EHLO localhost\x00d\x00a"));
		return;
	}
	else
    if ( smtpCode == 334 )
	{
		context.Write( wxBase64::Encode((const wxUint8*)(const char*)context.m_password.ToAscii(),
				context.m_password.Length()) );

		context.Write( wxT("\x00d\x00a"));

		context.ChangeState(g_passwState);
		context.TimerStart(wxSMTP_TIMEOUT_MAIL);
		return;
    }

	context.OnSetFailString(smtpRsp);
    context.ChangeState(g_quitState);
    context.SendQuit();
    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
}

void SendPasswState::onResponse(wxSMTP& context, int smtpCode, const wxString &smtpRsp) const
{
	context.TimerStop();

	if (smtpCode == 235)
	{
		context.ChangeState(g_sendMailFromState);
		context.SendFrom();
		context.TimerStart(wxSMTP_TIMEOUT_MAIL);
		return;
	}

	context.OnSetFailString(smtpRsp);
	context.ChangeState(g_quitState);
    context.SendQuit();
    context.TimerStart(wxSMTP_TIMEOUT_QUIT);
}

